/*    file: CTinyPlayer.cpp
 *    desc:
 * 
 * created: 2015-11-22
 *  author: chuanjiang.zh@qq.com
 * company: 
 */


#include "CTinyPlayer.h"
#include "TcpJsonAVFormat.h"
#include "TStringUtil.h"
#include "CLog.h"
#include "DateTime.h"

extern "C"
{
    #include "libavutil/time.h"
};


CTinyPlayer::CTinyPlayer():
    m_dict(),
    m_fmtContext(),
    m_videoIdx(-1),
    m_audioIdx(-1),
    m_psFormat(),
    m_position(-1)
{
    
}

CTinyPlayer::~CTinyPlayer()
{
    close();
}

int CTinyPlayer::setParam(const char* key, const char* value)
{
    return av_dict_set(&m_dict, key, value, 0);
}

int CTinyPlayer::open(const char* url)
{
    if (isOpen())
    {
        close();
    }

    comn::DateTime timeStart = comn::DateTime::now();
    CLog::info("begin open. %s\n", CLog::getShortTime());

    AVInputFormat* inputFmt = NULL;
    if (comn::StringUtil::startsWith(url, "ps://"))
    {
        std::string str = comn::StringUtil::getTail(url, "://");

        std::string ip;
        int port = 0;
        comn::StringUtil::split(str, ':', ip, port);

        m_psFormat = new TcpJsonAVFormat();
        m_psFormat->open(ip.c_str(), port);

        AVDictionaryEntry* entry = av_dict_get(m_dict, "json", NULL, 0);
        if (entry != NULL)
        {
            int rc = m_psFormat->play(entry->value);
            if (rc != 0)
            {
                close();
                return rc;
            }
        }

        m_fmtContext = avformat_alloc_context();
        m_fmtContext->pb = m_psFormat->create();

        inputFmt = av_find_input_format("mpeg");
    }
    else if (comn::StringUtil::startsWith(url, "http://"))
    {
        av_dict_set(&m_dict, "reconnect", "1", 0);
    }

    int rc = avformat_open_input(&m_fmtContext, url, inputFmt, &m_dict);
    if (rc != 0)
    {
        return rc;
    }

    m_fmtContext->debug = true;
    m_fmtContext->max_analyze_duration2 = m_fmtContext->max_analyze_duration = AV_TIME_BASE;
    m_fmtContext->probesize2 = m_fmtContext->probesize = 102400 / 2;

    rc = avformat_find_stream_info(m_fmtContext, NULL);
    if (rc != 0)
    {
        avformat_close_input(&m_fmtContext);
        return rc;
    }

    if (m_psFormat)
    {
        int64_t bytes = m_psFormat->getTotalBytes();
        CLog::info("total bytes: %I64d\n", bytes);
    }

    comn::DateTime timeNow = comn::DateTime::now();
    time_t ms = timeNow.subtract(timeStart);
    CLog::info("elapse of open url. %d\n", ms);

    int64_t duration = m_fmtContext->duration / AV_TIME_BASE;
    CLog::info("total duration: %d s\n", (int)duration);


    AVCodec* vCodec = NULL;
    AVCodec* aCodec = NULL;
    m_videoIdx = av_find_best_stream(m_fmtContext, AVMEDIA_TYPE_VIDEO, -1, -1, &vCodec, 0);
    m_audioIdx = av_find_best_stream(m_fmtContext, AVMEDIA_TYPE_AUDIO, -1, -1, &aCodec, 0);

    for (unsigned int i = 0; i < m_fmtContext->nb_streams; i ++)
    {
        AVStream* avstream = m_fmtContext->streams[i];
        if (avstream->codec->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            if (m_videoIdx <= 0)
            {
                m_videoIdx = i;
            }
        }
        else if (avstream->codec->codec_type == AVMEDIA_TYPE_AUDIO)
        {
            if (m_audioIdx <= 0)
            {
                m_audioIdx = i;
            }
        }
    }


    AVCodecID vCodecID = AV_CODEC_ID_NONE;
    if (m_videoIdx >= 0)
    {
        vCodecID = m_fmtContext->streams[m_videoIdx]->codec->codec_id;
    }

    AVCodecID aCodecID = AV_CODEC_ID_NONE;
    int samplerate = 0;
    int channels = 0;
    if (m_audioIdx >= 0)
    {
        aCodecID = m_fmtContext->streams[m_audioIdx]->codec->codec_id;
        channels = m_fmtContext->streams[m_audioIdx]->codec->channels;
        samplerate = m_fmtContext->streams[m_audioIdx]->codec->sample_rate;

        if ((aCodecID == AV_CODEC_ID_MP2) && channels == 0) 
        {
            aCodecID = AV_CODEC_ID_PCM_ALAW;
            channels = 1;
            samplerate = 8000;
        }
    }

    m_player.open(vCodecID, aCodecID, samplerate, channels);

    if (m_videoIdx >= 0)
    {
        AVStream* avstream = m_fmtContext->streams[m_videoIdx];
        if (avstream->codec->extradata_size > 0)
        {
            AVPacket pkt;
            av_init_packet(&pkt);
            av_new_packet(&pkt, avstream->codec->extradata_size);
            memcpy(pkt.data, avstream->codec->extradata, avstream->codec->extradata_size);

            MediaPacket mpkt;
            mpkt.data = pkt.data;
            mpkt.size = pkt.size;
            mpkt.duration = pkt.duration;
            mpkt.flags = pkt.flags;
            mpkt.ts = pkt.pts;
            mpkt.type = MEDIA_TYPE_VIDEO;

            m_player.input(mpkt);
        }
    }

    return start();
}

void CTinyPlayer::close()
{
    m_player.close();

    if (isRunning())
    {
        stop();
    }

    if (m_fmtContext)
    {
        avformat_close_input(&m_fmtContext);
    }

    m_videoIdx = -1;
    m_audioIdx = -1;

    if (m_dict)
    {
        av_dict_free(&m_dict);
    }

    if (m_psFormat)
    {
        m_psFormat->close();
        delete m_psFormat;
        m_psFormat = NULL;
    }
}

bool CTinyPlayer::isOpen()
{
    return m_player.isOpen();
}

void CTinyPlayer::setVideoWnd(HWND hwnd)
{
    m_player.setVideoWnd(hwnd);
}

int CTinyPlayer::run()
{
    AVPacket pkt;
    av_init_packet(&pkt);
    
    while (!m_canExit)
    {
        if (m_position >= 0)
        {
            int64_t duration = m_fmtContext->duration;

            int64_t timestamp = (int64_t)(duration * m_position);

            m_position = -1;

            av_seek_frame(m_fmtContext, -1, timestamp, 0);
        }

        int64_t tmBegin = av_gettime();
        int rc = av_read_frame(m_fmtContext, &pkt);
        int64_t readElapse = av_gettime() - tmBegin;

        if (rc != 0)
        {
            char errbuf[AV_ERROR_MAX_STRING_SIZE];
            av_strerror(rc, errbuf, AV_ERROR_MAX_STRING_SIZE);
            strcat_s(errbuf, AV_ERROR_MAX_STRING_SIZE, "\n");
            OutputDebugString(errbuf);

            if (rc == AVERROR_EOF)
            {
                if (m_eofDetector.isEof(readElapse, m_fmtContext))
                {
                    break;
                }
            }

            continue;
        }

        MediaPacket mpkt;
        mpkt.data = pkt.data;
        mpkt.size = pkt.size;
        mpkt.duration = pkt.duration;
        mpkt.flags = pkt.flags;
        mpkt.ts = pkt.pts;

        //CLog::info("pkt. pts:%I64d\n", pkt.pts);
    
        if (pkt.stream_index == m_videoIdx)
        {
            mpkt.type = MEDIA_TYPE_VIDEO;
            
            //int64_t timeOffset = pkt.pts - m_fmtContext->streams[pkt.stream_index]->start_time;
            //lastPts = av_rescale_q(timeOffset, m_fmtContext->streams[pkt.stream_index]->time_base, AV_TIME_BASE_Q);

            //if (m_fmtContext->streams[pkt.stream_index]->codec->codec_id == AV_CODEC_ID_H264)
            //{
            //    *((int32_t*)pkt.data) = 0x01000000; /// use prefix
            //}

            m_player.input(mpkt);
        }
        else if (pkt.stream_index == m_audioIdx)
        {
            mpkt.type = MEDIA_TYPE_AUDIO;
            m_player.input(mpkt);
        }
        else
        {
            // pass
        }

        av_free_packet(&pkt);
    }

    onStreamEnd();

    return 0;
}

bool CTinyPlayer::startup()
{
    return true;
}

void CTinyPlayer::cleanup()
{

}

void CTinyPlayer::doStop()
{

}

void CTinyPlayer::onStreamEnd()
{

}

int CTinyPlayer::seek(double position)
{
    m_position = position;

    return 0;
}